/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014 abel533@gmail.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.quick.crypt.test.base;

import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.type.TypeHandler;
import tk.mybatis.mapper.entity.EntityColumn;
import tk.mybatis.mapper.entity.EntityTable;
import tk.mybatis.mapper.mapperhelper.EntityHelper;

import java.util.*;

public class BeanCriteria {
	protected String orderByClause;

	protected boolean distinct;

	protected boolean exists;

	protected Set<String> selectColumns;

	protected List<Criteria> oredCriteria;

	protected Class<?> entityClass;

	protected EntityTable table;
	// 属性和列对应
	protected Map<String, EntityColumn> propertyMap;

	/**
	 * 默认exists为true
	 * 
	 * @param entityClass
	 */
	public BeanCriteria(Class<?> entityClass) {
		this(entityClass, true);
	}

	/**
	 * 带exists参数的构造方法
	 * 
	 * @param entityClass
	 * @param exists
	 *            - true时，如果字段不存在就抛出异常，false时，如果不存在就不使用该字段的条件
	 */
	public BeanCriteria(Class<?> entityClass, boolean exists) {
		this.exists = exists;
		oredCriteria = new ArrayList<Criteria>();
		this.entityClass = entityClass;
		table = EntityHelper.getEntityTable(entityClass);
		propertyMap = new HashMap<String, EntityColumn>(table.getEntityClassColumns().size());
		for (EntityColumn column : table.getEntityClassColumns()) {
			propertyMap.put(column.getProperty(), column);
		}
	}

	public Class<?> getEntityClass() {
		return entityClass;
	}

	public String getOrderByClause() {
		return orderByClause;
	}

	public void setOrderByClause(String orderByClause) {
		this.orderByClause = orderByClause;
	}

	public Set<String> getSelectColumns() {
		return selectColumns;
	}

	/**
	 * 指定要查询的属性列 - 这里会自动映射到表字段
	 * 
	 * @param properties
	 * @return
	 */
	public BeanCriteria selectProperties(String... properties) {
		if (properties != null && properties.length > 0) {
			if (this.selectColumns == null) {
				this.selectColumns = new LinkedHashSet<String>();
			}
			for (String property : properties) {
				if (propertyMap.containsKey(property)) {
					this.selectColumns.add(propertyMap.get(property).getColumn());
				}
			}
		}
		return this;
	}

	public boolean isDistinct() {
		return distinct;
	}

	public void setDistinct(boolean distinct) {
		this.distinct = distinct;
	}

	public List<Criteria> getOredCriteria() {
		return oredCriteria;
	}

	public void or(Criteria criteria) {
		oredCriteria.add(criteria);
	}

	public Criteria or() {
		Criteria criteria = createCriteriaInternal();
		oredCriteria.add(criteria);
		return criteria;
	}

	public Criteria createCriteria() {
		Criteria criteria = createCriteriaInternal();
		if (oredCriteria.size() == 0) {
			oredCriteria.add(criteria);
		}
		return criteria;
	}

	protected Criteria createCriteriaInternal() {
		Criteria criteria = new Criteria(propertyMap, exists);
		return criteria;
	}

	public void clear() {
		oredCriteria.clear();
		orderByClause = null;
		distinct = false;
	}

	protected abstract static class GeneratedCriteria {
		protected List<Criterion> criteria;
		// 字段是否必须存在
		protected boolean exists;
		// 属性和列对应
		protected Map<String, EntityColumn> propertyMap;

		protected GeneratedCriteria(Map<String, EntityColumn> propertyMap) {
			this(propertyMap, true);
		}

		protected GeneratedCriteria(Map<String, EntityColumn> propertyMap, boolean exists) {
			super();
			this.exists = exists;
			criteria = new ArrayList<Criterion>();
			this.propertyMap = propertyMap;
		}

		private String column(String property) {
			if (propertyMap.containsKey(property)) {
				return propertyMap.get(property).getColumn();
			} else if (exists) {
// 				throw new RuntimeException("当前实体类不包含名为" + property + "的属性!");
				throw new RuntimeException("当前实体类不包含名为" + property + "的属性!");
			} else {
				return null;
			}
		}

		private String property(String property) {
			if (propertyMap.containsKey(property)) {
				return property;
			} else if (exists) {
// 				throw new RuntimeException("当前实体类不包含名为" + property + "的属性!");
				throw new RuntimeException("当前实体类不包含名为" + property + "的属性!");
			} else {
				return null;
			}
		}

		public boolean isValid() {
			return criteria.size() > 0;
		}

		public List<Criterion> getAllCriteria() {
			return criteria;
		}

		public List<Criterion> getCriteria() {
			return criteria;
		}

		protected void addCriterion(String condition) {
			if (condition == null) {
				throw new RuntimeException("Value for condition cannot be null");
			}
			if (condition.startsWith("null")) {
				return;
			}
			criteria.add(new Criterion(condition));
		}

		protected void addCriterion(String condition, Object value, String property) {
			if (value == null) {
				throw new RuntimeException("Value for " + property + " cannot be null");
			}
			if (property == null) {
				return;
			}
			criteria.add(new Criterion(condition, value));
		}

		protected void addCriterion(String condition, Object value1, Object value2, String property) {
			if (value1 == null || value2 == null) {
				throw new RuntimeException("Between values for " + property + " cannot be null");
			}
			if (property == null) {
				return;
			}
			criteria.add(new Criterion(condition, value1, value2));
		}

		public Criteria andIsNull(String property) {
			addCriterion(column(property) + " is null");
			return (Criteria) this;
		}

		public Criteria andIsNotNull(String property) {
			addCriterion(column(property) + " is not null");
			return (Criteria) this;
		}

		public Criteria andEqualTo(String property, Object value) {
			addCriterion(column(property) + " =", value, property(property));
			return (Criteria) this;
		}

		public Criteria andNotEqualTo(String property, Object value) {
			addCriterion(column(property) + " <>", value, property(property));
			return (Criteria) this;
		}

		public Criteria andGreaterThan(String property, Object value) {
			addCriterion(column(property) + " >", value, property(property));
			return (Criteria) this;
		}

		public Criteria andGreaterThanOrEqualTo(String property, Object value) {
			addCriterion(column(property) + " >=", value, property(property));
			return (Criteria) this;
		}

		public Criteria andLessThan(String property, Object value) {
			addCriterion(column(property) + " <", value, property(property));
			return (Criteria) this;
		}

		public Criteria andLessThanOrEqualTo(String property, Object value) {
			addCriterion(column(property) + " <=", value, property(property));
			return (Criteria) this;
		}

		public Criteria andIn(String property, List<?> values) {
			addCriterion(column(property) + " in", values, property(property));
			return (Criteria) this;
		}

		public Criteria andNotIn(String property, List<?> values) {
			addCriterion(column(property) + " not in", values, property(property));
			return (Criteria) this;
		}

		public Criteria andBetween(String property, Object value1, Object value2) {
			addCriterion(column(property) + " between", value1, value2, property(property));
			return (Criteria) this;
		}

		public Criteria andNotBetween(String property, Object value1, Object value2) {
			addCriterion(column(property) + " not between", value1, value2, property(property));
			return (Criteria) this;
		}

		public Criteria andLike(String property, String value) {
			addCriterion(column(property) + "  like", value, property(property));
			return (Criteria) this;
		}

		public Criteria andNotLike(String property, String value) {
			addCriterion(column(property) + "  not like", value, property(property));
			return (Criteria) this;
		}

		/**
		 * 手写条件
		 * 
		 * @param condition
		 *            例如 "length(countryname)<5"
		 * @return
		 */
		public Criteria andCondition(String condition) {
			addCriterion(condition);
			return (Criteria) this;
		}

		/**
		 * 手写左边条件，右边用value值
		 * 
		 * @param condition
		 *            例如 "length(countryname)="
		 * @param value
		 *            例如 5
		 * @return
		 */
		public Criteria andCondition(String condition, Object value) {
			criteria.add(new Criterion(condition, value));
			return (Criteria) this;
		}

		/**
		 * 手写左边条件，右边用value值
		 * 
		 * @param condition
		 *            例如 "length(countryname)="
		 * @param value
		 *            例如 5
		 * @param typeHandler
		 *            类型处理
		 * @return
		 */
		public Criteria andCondition(String condition, Object value, String typeHandler) {
			criteria.add(new Criterion(condition, value, typeHandler));
			return (Criteria) this;
		}

		/**
		 * 手写左边条件，右边用value值
		 * 
		 * @param condition
		 *            例如 "length(countryname)="
		 * @param value
		 *            例如 5
		 * @param typeHandler
		 *            类型处理
		 * @return
		 */
		public Criteria andCondition(String condition, Object value, @SuppressWarnings("rawtypes") Class<? extends TypeHandler> typeHandler) {
			criteria.add(new Criterion(condition, value, typeHandler.getCanonicalName()));
			return (Criteria) this;
		}

		/**
		 * 将此对象的不为空的字段参数作为相等查询条件
		 * 
		 * @param param
		 *            参数对象
		 * @Date 2015年7月17日 下午12:48:08
		 */
		public Criteria andEqualTo(Object param) {
			MetaObject metaObject = SystemMetaObject.forObject(param);
			String[] properties = metaObject.getGetterNames();
			for (String property : properties) {
				// 属性和列对应Map中有此属性
				if (propertyMap.get(property) != null) {
					Object value = metaObject.getValue(property);
					// 属性值不为空
					if (value != null) {
						andEqualTo(property, value);
					}
				}
			}
			return (Criteria) this;
		}
	}

	public static class Criteria extends GeneratedCriteria {
		protected Criteria(Map<String, EntityColumn> propertyMap) {
			super(propertyMap);
		}

		protected Criteria(Map<String, EntityColumn> propertyMap, boolean exists) {
			super(propertyMap, exists);
		}
	}

	public static class Criterion {
		private String condition;

		private Object value;

		private Object secondValue;

		private boolean noValue;

		private boolean singleValue;

		private boolean betweenValue;

		private boolean listValue;

		private String typeHandler;

		protected Criterion(String condition) {
			super();
			this.condition = condition;
			this.typeHandler = null;
			this.noValue = true;
		}

		protected Criterion(String condition, Object value, String typeHandler) {
			super();
			this.condition = condition;
			this.value = value;
			this.typeHandler = typeHandler;
			if (value instanceof List<?>) {
				this.listValue = true;
			} else {
				this.singleValue = true;
			}
		}

		protected Criterion(String condition, Object value) {
			this(condition, value, null);
		}

		protected Criterion(String condition, Object value, Object secondValue, String typeHandler) {
			super();
			this.condition = condition;
			this.value = value;
			this.secondValue = secondValue;
			this.typeHandler = typeHandler;
			this.betweenValue = true;
		}

		protected Criterion(String condition, Object value, Object secondValue) {
			this(condition, value, secondValue, null);
		}

		public String getCondition() {
			return condition;
		}

		public Object getValue() {
			return value;
		}

		public Object getSecondValue() {
			return secondValue;
		}

		public boolean isNoValue() {
			return noValue;
		}

		public boolean isSingleValue() {
			return singleValue;
		}

		public boolean isBetweenValue() {
			return betweenValue;
		}

		public boolean isListValue() {
			return listValue;
		}

		public String getTypeHandler() {
			return typeHandler;
		}
	}
}

